﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using MarioArena.Animation;
using MarioArena.Characters;
using MarioArena.Multiplayer.TCP;
using MarioArena.World.Projectiles;

namespace MarioArena.World
{
    class Player
    {
        public int Id { get; set; }
        public int Ping { get; set; }
        public bool IsReadyInLobby { get; set; }
        public bool HasAiEnabled { get; set; }
        
        public bool LeftKeyPressed { get; set; }
        public bool RightKeyPressed { get; set; }
        public bool JumpKeyPressed { get; set; }
        public bool LeftMouseButtonPressed { get; set; }
        public bool RightMouseButtonPressed { get; set; }

        /// <summary>
        /// The absolute position on screen of the origin
        /// </summary>
        public Vector2 AbsoluteProjectileOrigin { get; set; }

        private int mPressedKey = 0;
        private bool mDecelerateRight = false;
        private bool mDecelerateLeft = false;

        public Character Character { get; set; }
        private CooldownBar cooldownBar;
        public Crosshair Crosshair;
        private AiAgent mAiAgent;

        /// <summary>
        /// The elapsed time between the two latest updates. This is used to create machine-independance. 
        /// The player will move just as fast on a very slow PC as on a fast pc.
        /// </summary>
        private float mElapsedTime = 0.016667f;
        private long mTimeLastProjectile;
        private long mTimeLastSpecialProjectile;

        #region Singleton
        private static Player instance = new Player();
        public static Player Instance
        {
            get
            {
                return instance;
            }
        }
        private Player()
        {
        }
        #endregion

        /// <summary>
        /// Loads the content and initializes the player.
        /// </summary>
        public void LoadContent()
        {
            Character = new Mario(true);
            this.AbsoluteProjectileOrigin = new Vector2(this.Character.ProjectileOrigin.X - Camera.X + GameManager.Instance.HalfScreenWidth,
                this.Character.ProjectileOrigin.Y - Camera.Y + GameManager.Instance.HalfScreenHeight);

            this.cooldownBar = new CooldownBar(Character, true);
            this.Crosshair = new Crosshair(this);
            this.mAiAgent = new AiAgent();
        }

        /// <summary>
        /// Updates the player.
        /// </summary>
        /// <param name="isActive">Indicates whether this application is currently active (focussed) or not. 
        /// This is used to prevent catching input even when another application is currently being used.</param>
        public void Update(GameTime gameTime, bool isActive)
        {
            if (!this.Character.IsDead && !GameManager.Instance.GameHasEnded) //only update if alive
            {
                // Elapsed time between this and the last update in seconds (< 0)
                this.mElapsedTime = (float)(gameTime.ElapsedGameTime.TotalSeconds);

                HandleInput(isActive);
            }
            //Update cooldown bar with the last time the special was fired, and the cooldown it takes until it can be fired again.
            this.cooldownBar.Update(this.mTimeLastSpecialProjectile, SpecialFireball.FireDelay);
            this.Character.Update(gameTime);

            if (!this.Character.IsDead && !GameManager.Instance.GameHasEnded)
            {
                this.Crosshair.Update(isActive);

                Camera.X += Convert.ToInt32((this.Character.SpeedX * Character.Direction) * mElapsedTime);
                Camera.Y += Convert.ToInt32(this.Character.SpeedY * mElapsedTime);

                this.Character.CameraX = Camera.X;
                this.Character.CameraY = Camera.Y;

                // Set the correct bounds depending on the new Camera position
                this.Character.Bounds = new Rectangle(Camera.X - (this.Character.Width / 2), Camera.Y - this.Character.MarginTop, this.Character.Width, this.Character.Height);

                // Set the correct projectile origin
                this.Character.ProjectileOrigin = new Vector2(this.Character.Bounds.Center.X, this.Character.Bounds.Center.Y);
               
                this.AbsoluteProjectileOrigin = new Vector2(this.Character.ProjectileOrigin.X - Camera.X + GameManager.Instance.HalfScreenWidth,
                    this.Character.ProjectileOrigin.Y - Camera.Y + GameManager.Instance.HalfScreenHeight);

                this.mAiAgent.Update();
                this.HasAiEnabled = this.mAiAgent.Enabled;
            }
        }

        /// <summary>
        /// Draws the player.
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            this.Character.Draw(spriteBatch);
            if (!this.Character.IsDead) //only draw if alive
            {
                this.cooldownBar.Draw(spriteBatch);
                this.mAiAgent.Draw(spriteBatch);
            }
        } 

        /// <summary>
        /// Handles the input reponse for the player.
        /// </summary>
        private void HandleInput(bool isActive)
        {
            KeyboardState keyboardState = Keyboard.GetState();
            MouseState mouseState = Mouse.GetState();

            //If AI not not enabled, input should be retrieved from the keyboard.
            if (!this.mAiAgent.Enabled)
            {
                //Update every input key
                this.LeftKeyPressed = keyboardState.IsKeyDown(Keys.A);
                this.RightKeyPressed = keyboardState.IsKeyDown(Keys.D);
                this.JumpKeyPressed = keyboardState.IsKeyDown(Keys.W);
                if (!this.JumpKeyPressed) //Secondary jump key
                    this.JumpKeyPressed = keyboardState.IsKeyDown(Keys.Space);
                this.LeftMouseButtonPressed = (mouseState.LeftButton == ButtonState.Pressed);
                this.RightMouseButtonPressed = (mouseState.RightButton == ButtonState.Pressed);
            }


            // Keys input
            if ((LeftKeyPressed || RightKeyPressed) && isActive)
            {
                if (LeftKeyPressed) //LEFT pressed
                {
                    mDecelerateLeft = false; //Player is not decelerating in this direction anymore

                    if (this.Character.Direction == 0) this.Character.Direction = -1; //first press
                    if (mPressedKey == 0) mPressedKey = -1;
                    if (mPressedKey == 1) //If previous key was D
                        mDecelerateRight = true; //Start decelerating in the right direction
                    mPressedKey = -1;
                }
                else if (RightKeyPressed) //RIGHT pressed
                {
                    mDecelerateRight = false; //Player is not decelerating in this direction anymore

                    if (this.Character.Direction == 0) this.Character.Direction = 1; //first press
                    if (mPressedKey == 0) mPressedKey = 1;
                    if (mPressedKey == -1) //If previous key was A
                        mDecelerateLeft = true; //Start decelerating in the left direction
                    mPressedKey = 1;
                }

                if (mDecelerateRight) //If busy decelerating in the right direction
                {
                    this.Character.SpeedX -= (this.Character.TurningDecelerationX * this.Character.AccelerationScale) * this.mElapsedTime; //Decelerate
                    if (this.Character.SpeedX < (this.Character.TurningDecelerationX * this.Character.AccelerationScale) * this.mElapsedTime) //When (nearly) standing still...
                    {
                        mDecelerateRight = false; //... stop deceleration
                        this.Character.Direction = -1; //Set new direction for acceleration
                    }
                }
                else if (mDecelerateLeft) //If busy descelerating in the left direction
                {
                    this.Character.SpeedX -= (this.Character.TurningDecelerationX * this.Character.AccelerationScale) * this.mElapsedTime; //Descelerate
                    if (this.Character.SpeedX <= (this.Character.TurningDecelerationX * this.Character.AccelerationScale) * this.mElapsedTime) //When (nearly) standing still...
                    {
                        mDecelerateLeft = false; //... stop deceleration
                        this.Character.Direction = 1; //Set new direction for acceleration
                    }
                }
                else this.Character.SpeedX += (this.Character.AccelerationX * this.Character.AccelerationScale) * this.mElapsedTime;// Accelerate horizontally

                if (this.Character.SpeedX > this.Character.MaxSpeedX)
                    this.Character.SpeedX = this.Character.MaxSpeedX;
            }

            if (JumpKeyPressed && isActive)
            {
                // Only jump if not already in air
                if (this.Character.PlayerState != PlayerState.Falling && this.Character.PlayerState != PlayerState.Jumping)
                {
                    if (this.Character.IsTileIndexInBounds())
                    {
                        Tile bottomTile = TileEngine.Instance.Tiles[this.Character.TileIndex.X, this.Character.TileIndex.Y + 1];
                        Tile bottomLeftTile = TileEngine.Instance.Tiles[this.Character.TileIndex.X - 1, this.Character.TileIndex.Y + 1];
                        Tile bottomRightTile = TileEngine.Instance.Tiles[this.Character.TileIndex.X + 1, this.Character.TileIndex.Y + 1];
                        if (bottomTile.Texture != null || bottomLeftTile.Texture != null || bottomRightTile.Texture != null)
                        {
                            this.Character.PlayerState = PlayerState.Jumping;
                            this.Character.SpeedY = -this.Character.MaxSpeedY;
                        }
                    }
                }
            }

            // NOT in air
            if (!LeftKeyPressed && !RightKeyPressed && // No horizontal input and
                this.Character.PlayerState != PlayerState.Falling && this.Character.PlayerState != PlayerState.Jumping) // Player is on the ground
            {
                // Decelerate horizontally
                this.Character.SpeedX -= this.Character.DecelerationX * this.mElapsedTime;
                if (this.Character.SpeedX < 0)
                    this.Character.SpeedX = 0;
            }
            else // In air
            {
                // Accelerate vertically
                this.Character.SpeedY += this.Character.AccelerationY * this.mElapsedTime;
                if (this.Character.SpeedY > this.Character.MaxSpeedY)
                    this.Character.SpeedY = this.Character.MaxSpeedY;
                if (this.Character.PlayerState == PlayerState.Falling)
                {
                    if (this.Character.SpeedY < 0)
                        this.Character.PlayerState = PlayerState.Jumping;
                }
                else if (this.Character.PlayerState == PlayerState.Jumping)
                {
                    if (this.Character.SpeedY > 0)
                        this.Character.PlayerState = PlayerState.Falling;
                }
            }

            // Left mouse button input
            if (LeftMouseButtonPressed && isActive)
            {
                Shoot();
            }

            // Right mouse button input
            else if (RightMouseButtonPressed && isActive)
            {
                ShootSpecial();
            }
        }

        protected void Shoot()
        {
            long currentTime = DateTime.Now.Ticks / 10000;

            int fireDelay = Fireball.FireDelay;
            // if (other character) firedelay = ...

            if (this.mTimeLastProjectile.Equals(0))
                this.mTimeLastProjectile = currentTime - fireDelay;

            if (currentTime - this.mTimeLastProjectile >= fireDelay)
            {
                this.mTimeLastProjectile = currentTime;

                // Fire a projectile
                string bullettype = "10";

                // Make sure the server doesn't use this client's screen size for calculations.
                // We add it here because it has been subtracted earlier in the client.
                float cameraX = this.Character.ProjectileOrigin.X + (float)(GameManager.GraphicsDevice.Viewport.Width / 2);
                float cameraY = this.Character.ProjectileOrigin.Y + (float)(GameManager.GraphicsDevice.Viewport.Height / 2);
                TCPConnection.Instance.Send("21_" + bullettype + "_" + cameraX + "_" + cameraY + "_" + this.Crosshair.Direction.X + "_" + this.Crosshair.Direction.Y + "_" + Fireball.Speed);
                //TCPConnection.Instance.Send("21_" + bullettype + "_" + this.Character.ProjectileOrigin.X + "_" + this.Character.ProjectileOrigin.Y + "_" + this.mCrosshair.Direction.X + "_" + this.mCrosshair.Direction.Y);
            }
        }

        protected void ShootSpecial()
        {
            long currentTime = DateTime.Now.Ticks / 10000;

            int fireDelay = SpecialFireball.FireDelay;
            // if (other character) firedelay = ...

            if (this.mTimeLastSpecialProjectile.Equals(0))
                this.mTimeLastSpecialProjectile = currentTime - fireDelay;

            if (currentTime - this.mTimeLastSpecialProjectile >= fireDelay)
            {
                this.mTimeLastSpecialProjectile = currentTime;

                // Make sure the normal fireball cannot be fired at the same time: reset its delay
                this.mTimeLastProjectile = currentTime;

                // Fire a projectile
                string bullettype = "11";

                // Make sure the server doesn't use this client's screen size for calculations.
                // We add it here because it has been subtracted earlier in the client.
                int cameraX = (int)this.Character.ProjectileOrigin.X + (GameManager.GraphicsDevice.Viewport.Width / 2);
                int cameraY = (int)this.Character.ProjectileOrigin.Y + (GameManager.GraphicsDevice.Viewport.Height / 2);


                // Build and send the TCP message
                string projectileData = "21_" + bullettype + "_" + cameraX + "_" + cameraY + "_" + this.Crosshair.Direction.X + "_" + this.Crosshair.Direction.Y + "_" + SpecialFireball.Speed;
                TCPConnection.Instance.Send(projectileData);
            }
        }

        #region Artificial Intelligence

        /// <summary>
        /// The AI-agent, which takes over the player's controls when activated, automatically 
        /// following a path.
        /// </summary>
        private class AiAgent
        {
            /// <summary>
            /// The number of A*-iterations TBA* executes every update
            /// </summary>
            private const int TbaIterations = 10;
            private const int JumpDistance = 5; //Maximum jump distance in tiles when no speed is built up.
            private const int JumpHeight = 4; //Maximum jump height in tiles

            public bool Enabled = false;

            private HashSet<PathfindingTile> open = new HashSet<PathfindingTile>();
            private HashSet<PathfindingTile> closed = new HashSet<PathfindingTile>();
            private List<PathfindingTile> path = new List<PathfindingTile>();
            private List<PathfindingTile> pathToFollow = new List<PathfindingTile>();

            private bool solutionFound = false;
            private PathfindingTile start;
            private PathfindingTile goal;
            private PathfindingTile currentLocation;
            private PathfindingTile lastLocation;

            private PathfindingTile jumpTarget;
            private PathfindingTile fallTarget;
            private List<PathfindingTile> drop = new List<PathfindingTile>();  

            private Texture2D mDummyTexture;
            private KeyboardState mLastKeyboardState;
            private bool mHasStarted = false;

            private PathfindingTile mNextTile;

            /// <summary>
            /// Constructs a new AI agent.
            /// </summary>
            public AiAgent()
            {
                this.mDummyTexture = new Texture2D(GameManager.GraphicsDevice, 1, 1);
                this.mDummyTexture.SetData(new Color[] { Color.White });

                mLastKeyboardState = Keyboard.GetState();
            }

            /// <summary>
            /// Updates the AI agent.
            /// </summary>
            public void Update()
            {
                // Enable or disable on "P"-key press
                KeyboardState currentKeyboardState = Keyboard.GetState();
                if (mLastKeyboardState.IsKeyDown(Keys.P) && currentKeyboardState.IsKeyUp(Keys.P))
                {
                    if (this.Enabled) //AI is activated
                    {
                        this.Enabled = false;
                    }
                    else
                    {
                        this.Enabled = true; //AI is disabled
                        this.mHasStarted = false; //Stop AI so it will create a new route next time it is toggled
                    }
                    //Reset all input, so the keyboard input is forgotten.
                    Player.Instance.LeftKeyPressed = false;
                    Player.Instance.RightKeyPressed = false;
                    Player.Instance.JumpKeyPressed = false;
                    Player.Instance.LeftMouseButtonPressed = false;
                    Player.Instance.RightMouseButtonPressed = false;
                }
                mLastKeyboardState = currentKeyboardState;
                
                // Execute AI
                if (Enabled)
                {
                    if (mHasStarted == false)
                    {
                        if (TileEngine.Instance.IsGridInitialized)
                        {
                            int playerX = Player.Instance.Character.TileIndex.X;
                            int playerY = Player.Instance.Character.TileIndex.Y;
                            // Only initialize a new path if the character is standing on a wall tile
                            if (playerY + 1 < TileEngine.Instance.LevelHeight
                                && TileEngine.Instance.JumpingGrid[playerX, playerY + 1].IsWall)
                            {
                                InitializeRandomPath();
                                mHasStarted = true;
                                Console.WriteLine("New path");
                            }
                        }
                    }

                    if (mHasStarted)
                        TbaStar();
                    //Make AI move player over best found path
                    FollowBestPath();
                    //Shoot at random target
                    Shoot();
                }
            }

            private void InitializeRandomPath()
            {
                //Find a random location to use as target
                List<PathfindingTile> possibleTargetLocations = new List<PathfindingTile>();
                foreach (PathfindingTile tile in TileEngine.Instance.JumpingGrid)
                {
                    if (tile.IsEligible) //if tile can contain a path
                    {
                        //if tile beneath is a wall, and tile above is not (so the player can actually stand there)
                        if (TileIsWall(tile.X, tile.Y + 1) && !TileIsWall(tile.X, tile.Y - 1))
                            possibleTargetLocations.Add(tile);
                    }
                }
                int randomIndex = new Random().Next(possibleTargetLocations.Count);
                PathfindingTile targetTile = possibleTargetLocations[randomIndex];
                //Initialize a new path to the random location
                /*InitializeNewPath(new Point(Player.Instance.Character.TileIndex.X, Player.Instance.Character.TileIndex.Y),
                    new Point(targetTile.X, targetTile.Y));*/
                //DEMO VERSION
                InitializeNewPath(new Point(Player.Instance.Character.TileIndex.X, Player.Instance.Character.TileIndex.Y),
                    new Point(147, 48));
            }

            /// <summary>
            /// Follow the best path, moving the character.
            /// </summary>
            private void FollowBestPath()
            {
                // Reset input
                Player.Instance.JumpKeyPressed = false;
                Player.Instance.LeftKeyPressed = false;
                Player.Instance.RightKeyPressed = false;

                PlayerState currentState = Player.Instance.Character.PlayerState;
                int playerX = Player.Instance.Character.TileIndex.X;
                int playerY = Player.Instance.Character.TileIndex.Y;

                // Reset jumping to tile if a jump has finished, whether the jumping destination has been reached or not
                if (currentState == PlayerState.Passive || currentState == PlayerState.Running)
                {
                    // TODO: Nieuw pad maken wanneer de jumping destination niet bereikt is met deze sprong
                    jumpTarget = null;
                }

                if (pathToFollow.Count > 0)
                {
                    PathfindingTile nextTile = jumpTarget;
                    if (jumpTarget == null) // If not currently jumping to a tile
                    {
                        bool currentHasFloor = false;
                        bool nextHasFloor = false;

                        // Get the front tile of the (remaining) path
                        nextTile = pathToFollow.Last();

                        // Check if the current tile has a wall below it
                        if (currentLocation.Y + 1 < TileEngine.Instance.LevelHeight // if tile below is not out of bounds
                            && TileEngine.Instance.JumpingGrid[currentLocation.X, currentLocation.Y + 1].IsWall)
                        {
                            currentHasFloor = true;
                        }

                        // Check if the next tile has a wall below it
                        if (nextTile.Y + 1 < TileEngine.Instance.LevelHeight
                            && TileEngine.Instance.JumpingGrid[nextTile.X, nextTile.Y + 1].IsWall) 
                        {
                            nextHasFloor = true;
                        }

                        // Check if the next tile is in the air, and the current isn't yet
                        // Or if the next tile is simply HIGHER than the current tile
                        if (currentHasFloor && !nextHasFloor || nextTile.Y < playerY)
                        {
                            
                            // JUMP
                            // If not already jumping or falling
                            if (Player.Instance.Character.PlayerState != PlayerState.Jumping
                                && Player.Instance.Character.PlayerState != PlayerState.Falling)
                            {
                                PathfindingTile nearestWalkableTile = GetJumpDestination();
                                // Only jump is no wall above is blocking the player
                                if (!(nearestWalkableTile != null && IsWallBetween(nearestWalkableTile.X, nearestWalkableTile.Y)))
                                {
                                    jumpTarget = GetJumpDestination();
                                    if (jumpTarget != null)
                                    {
                                        Player.Instance.JumpKeyPressed = true;
                                    }
                                    else // Could not jump to a target tile
                                    {
                                        // Walk to some other tile first before jumping
                                    }
                                }
                            }
                        }
                        //if still no jump is detected at this point, check if the ai will fall down and stop that by jumping past it.
                        DetectAndPassRavines(nextTile, currentHasFloor, nextHasFloor, playerY);
                    }

                    this.mNextTile = nextTile;


                    //int halfCharacterWidth = Player.Instance.Character.Width / 2;
                    int playerMid = Player.Instance.Character.CameraX + GameManager.Instance.HalfScreenWidth; // center of the player
                    int tileMid = (nextTile.X * Tile.Width) + (Tile.Width / 2); // center of the tile

                    float speedX = Player.Instance.Character.SpeedX;
                    float decelerationX = Player.Instance.Character.DecelerationX;
                    // The distance that will be travelled during the deceleration process before coming to a stop
                    int decelerationLength = (int)((speedX * 0.5f) * (Player.Instance.Character.MaxSpeedX / decelerationX));

                    if (currentState != PlayerState.Jumping && currentState != PlayerState.Falling)
                    {
                        // EXECUTE MOVEMENT HORIZONTALLY, AND PREVENT FALLING OFF PLATFORMS
                        Walk(playerMid, tileMid, playerX, playerY, decelerationLength);
                    }
                    else // Jumping or falling
                    {
                        MoveInJump(playerMid, tileMid, playerY, nextTile.Y, decelerationLength);
                    }
                }
            }

            private void DetectAndPassRavines(PathfindingTile nextTile, bool currentHasFloor, bool nextHasFloor, int playerY)
            {
                if (jumpTarget == null)
                {
                    // If next tile is in the air, and currently standing on a platform
                    // If the next tile in the route is LOWER than the current tile (a drop/fall)
                    if (currentHasFloor && !nextHasFloor && nextTile.Y >= playerY)
                    {
                        drop.Add(nextTile);
                        //Console.WriteLine("DROP!");
                        bool endOfDropFound = false;
                        bool endOfDropIsFloating = false;
                        PathfindingTile nextTileInRoute = nextTile;
                        PathfindingTile previousTile = currentLocation;
                        //we already checked the first item (Count -1), so start at Count -2
                        int counter = pathToFollow.Count - 2;
                        drop.Clear();
                        while (!endOfDropFound && counter >= 0)
                        {
                            previousTile = nextTileInRoute;
                            nextTileInRoute = pathToFollow[counter];
                            if (!endOfDropFound)
                            {
                                //if path is still going down
                                if (nextTileInRoute.X.Equals(nextTile.X))
                                    drop.Add(nextTileInRoute);
                                else
                                {
                                    endOfDropFound = true;
                                    //check if last tile of the drop is floating
                                    if (previousTile.Y + 1 < TileEngine.Instance.LevelHeight && !TileEngine.Instance.JumpingGrid[previousTile.X, previousTile.Y + 1].IsWall)
                                    {
                                        endOfDropIsFloating = true;
                                    }
                                    //compensate counter, since it will be decreased while it shouldnt.
                                    counter++;
                                }
                            }
                            counter--;
                        }

                        //if a drop was just found and it is not touching the ground, 
                        //find out how to jump past it by following the path even further.
                        if (endOfDropFound && endOfDropIsFloating)
                        {
                            //continue searching through the path, starting where we stopped in the previous loop (same counter)
                            bool jumpTargetFound = false;
                            while (!jumpTargetFound && counter >= 0)
                            {
                                nextTileInRoute = pathToFollow[counter];
                                if (!jumpTargetFound)
                                {
                                    
                                    //if the tile below is wall
                                    if (TileIsWall(nextTileInRoute.X, nextTileInRoute.Y + 1))
                                    {
                                        fallTarget = nextTileInRoute;
                                       
                                        // The tile below is a wall. Check if that tile is reachable by jumping
                                         if (TileWithinJumpRange(fallTarget.X, fallTarget.Y))
                                        {
                                            jumpTargetFound = true;
                                            Console.WriteLine("Found jump target to skip ravine.");
                                            this.jumpTarget = fallTarget;
                                            Player.Instance.JumpKeyPressed = true;
                                        }
                                    }
                                }
                                counter--;
                            }
                        }
                    }
                }
            }

            /// <summary>
            /// Lets the character walk on the ground if allowed.
            /// </summary>
            /// <param name="playerMid">The camera position of the middle of the player.</param>
            /// <param name="tileMid">The camera position of the middle of the tile.</param>
            /// <param name="playerIndexY">The player's tileIndex.Y .</param>
            /// <param name="decelerationLength">The distance the player would travel if the player were
            /// to start decelerating at this speed.</param>
            private void Walk(int playerMid, int tileMid, int playerIndexX, int playerIndexY, int decelerationLength)
            {
                // Check for movement to the LEFT
                if (playerMid > tileMid && playerIndexY + 1 < TileEngine.Instance.LevelHeight)
                {
                    // The tile X-index at which the player would stop if to start decelerating now
                    int tileXToCheck = (playerMid - decelerationLength) / Tile.Width;

                    // Check if the TILE TO THE LEFT of this index has no wall tile beneath it (the platform ends)
                    tileXToCheck -= 1;

                    if (tileXToCheck >= 0)
                    {
                        // NOG VOOR ELKE TILE ERTUSSEN CHECKEN?
                        PathfindingTile tileBelowPlayer = TileEngine.Instance.JumpingGrid[playerIndexX, playerIndexY + 1];
                        PathfindingTile topLeftOfPlatform = TileEngine.Instance.JumpingGrid[tileXToCheck, playerIndexY];
                        PathfindingTile leftOfPlatform = TileEngine.Instance.JumpingGrid[tileXToCheck, playerIndexY + 1];

                        // Check if the player is standing on a platform, and  neither the platform's top left nor the left tile are walls
                        if (tileBelowPlayer.IsWall && !topLeftOfPlatform.IsWall && !leftOfPlatform.IsWall
                            && !pathToFollow.Contains(topLeftOfPlatform) && !pathToFollow.Contains(leftOfPlatform)
                            && playerMid - tileMid <= decelerationLength)
                        {
                            // The platform is ending, decelerate!
                            Console.WriteLine("PLATFORM ENDING.");
                        }
                        else
                        {
                            MoveLeft();
                        }
                    }
                    else if (tileXToCheck < 0)
                    {
                        //left side of level is reached, but that is still an end of the platform. So stop Ai.
                    }
                }

                // Check for movement to the RIGHT
                else if (playerMid < tileMid && playerIndexY + 1 < TileEngine.Instance.LevelHeight)
                {
                    // The tile X-index at which the player would stop if to start decelerating now
                    int tileXToCheck = (playerMid + decelerationLength) / Tile.Width;

                    // Check if the TILE TO THE RIGHT of this index has no wall tile beneath it (the platform ends)
                    tileXToCheck += 1;

                    if (tileXToCheck < TileEngine.Instance.LevelWidth)
                    {
                        // NOG VOOR ELKE TILE ERTUSSEN CHECKEN?
                        PathfindingTile tileBelowPlayer = TileEngine.Instance.JumpingGrid[playerIndexX, playerIndexY + 1];
                        PathfindingTile topRightOfPlatform = TileEngine.Instance.JumpingGrid[tileXToCheck, playerIndexY];
                        PathfindingTile rightOfPlatform = TileEngine.Instance.JumpingGrid[tileXToCheck, playerIndexY + 1];

                        // Check if the player is standing on a platform, and  neither the platform's top right nor the right tile are walls
                        if (tileBelowPlayer.IsWall && !topRightOfPlatform.IsWall && !rightOfPlatform.IsWall
                            && !pathToFollow.Contains(topRightOfPlatform) && !pathToFollow.Contains(rightOfPlatform)
                            && tileMid - playerMid <= decelerationLength)
                        {
                            // The platform is ending, decelerate!
                            Console.WriteLine("PLATFORM ENDING.");
                        }
                        else
                        {
                            MoveRight();
                        }
                    }
                    else if (tileXToCheck >= TileEngine.Instance.LevelHeight)
                    {
                        //left side of level is reached, but that is still an end of the platform. So stop Ai.
                    }
                }
            }


            private void MoveInJump(int playerMid, int tileMid, int playerIndexY, int tileIndexY, int decelerationLength)
            {
                // While below the platform to jump on, make sure we don't move under that platform
                if (playerIndexY > tileIndexY)
                {
                    // Move to the left while jumping
                    if (playerMid > tileMid)
                    {
                        // Make sure the character doesn't move further than the middle of the tile 
                        // next to the jumping target tile (+ Tile.Width).
                        if (playerMid - decelerationLength > tileMid + Tile.Width)
                        {
                            MoveLeft();
                        }
                    }

                    // Move to the right while jumping
                    else if (playerMid < tileMid)
                    {
                        if (playerMid + decelerationLength < tileMid - Tile.Width)
                        {
                            MoveRight();
                        }
                    }
                }
                else // Move onto the platform
                {
                    if (playerMid > tileMid) // Player is to the right of the tile
                    {
                        // Make sure the character doesn't move further than the middle of the
                        // jumping target tile.
                        if (playerMid - decelerationLength > tileMid)
                        {
                            MoveLeft();
                        }

                        // -- remove for inexact jumping
                        else
                        {
                                MoveRight();
                        }
                        // -----------------------------
                    }
                    else if (playerMid < tileMid) // Player is to the left of the tile
                    {
                        if (playerMid + decelerationLength < tileMid)
                        {
                            MoveRight();
                        }

                        // -- remove for inexact jumping
                        else
                        {
                            MoveLeft();
                        }
                        // -----------------------------
                    }
                }
            }

            /// <summary>
            /// Get a jump destination tile, which can be jumped to from the player's current position.
            /// </summary>
            /// <returns>A tile to jump to.</returns>
            private PathfindingTile GetJumpDestination()
            {
                if (pathToFollow.Count > 1)
                {
                    // Find the nearest tile in the path which is positioned on top of a wall tile
                    for (int i = pathToFollow.Count - 1; i >= 0; i--)
                    {
                        PathfindingTile tile = pathToFollow[i];
                        // if difference between tileX and playerX > 1
                        if (!tile.IsStart)
                        {
                            int playerX = Player.Instance.Character.TileIndex.X;
                            int playerY = Player.Instance.Character.TileIndex.Y;

                            // Check if the tile is on top of a wall corner
                            if (TileIsWall(tile.X, tile.Y + 1))
                            {
                                if(TileWithinJumpRange(tile.X, tile.Y))
                                {
                                    return tile;
                                }
                            }
                        }
                    }
                }
                return null;
            }

            /// <summary>
            /// Checks if a tile is a wall. Will automatically check when tile index is in range.
            /// </summary>
            /// <param name="tileIndexX">X index of the tile to check</param>
            /// <param name="tileIndexY">Y index of the tile to check</param>
            /// <returns>Returns true if the tile is a wall. Returns false if not, or if the tile doesnt exist in the level grid.</returns>
            private bool TileIsWall(int tileIndexX, int tileIndexY)
            {
                //check if index is in range of the level grid.
                if (tileIndexX >= 0 && tileIndexX < TileEngine.Instance.LevelWidth
                    && tileIndexY >= 0 && tileIndexY < TileEngine.Instance.LevelHeight)
                {
                    //return true if that tile is a wall.
                    if (TileEngine.Instance.JumpingGrid[tileIndexX, tileIndexY].IsWall)
                        return true;
                }
                return false;
            }

            /// <summary>
            /// Checks if a tile is within jumprange of the player.
            /// </summary>
            /// <param name="tileIndexX">X index of the tile to check</param>
            /// <param name="tileIndexY">Y index of the tile to check</param>
            /// <returns>Returns true if the tile is within jumping range of the player</returns>
            private bool TileWithinJumpRange(int tileIndexX, int tileIndexY)
            {
                if (Math.Abs(tileIndexX - Player.Instance.Character.TileIndex.X) <= (Settings.JumpDistance * 2)
                                            && Math.Abs(tileIndexY - Player.Instance.Character.TileIndex.Y) <= JumpHeight)
                {
                    return true;
                }
                return false;
            }

            /// <summary>
            /// Vertically checks whether there is a wall tile between the player and a tile.
            /// </summary>
            /// <param name="playerX">The X-position of the player.</param>
            /// <param name="playerY">The Y-position of the player.</param>
            /// <param name="tileY">The Y-position of the tile.</param>
            private bool IsWallBetween(int tileX, int tileY)
            {
                int playerX = Player.Instance.Character.TileIndex.X;
                int playerY = Player.Instance.Character.TileIndex.Y;
                Rectangle playerBounds = Player.Instance.Character.Bounds;

                int halfCharacterWidth = Player.Instance.Character.Width / 2;
                int leftPlayerBoundsIndex = ((Player.Instance.Character.CameraX + GameManager.Instance.HalfScreenWidth) - halfCharacterWidth) / Tile.Width;
                int rightPlayerBoundsIndex = ((Player.Instance.Character.CameraX + GameManager.Instance.HalfScreenWidth) + halfCharacterWidth) / Tile.Width;
                if (leftPlayerBoundsIndex < 0) leftPlayerBoundsIndex = 0;
                if (rightPlayerBoundsIndex < 0) rightPlayerBoundsIndex = 0;
                if (leftPlayerBoundsIndex >= TileEngine.Instance.LevelWidth)
                    leftPlayerBoundsIndex = TileEngine.Instance.LevelWidth - 1;
                if (rightPlayerBoundsIndex >= TileEngine.Instance.LevelWidth)
                    rightPlayerBoundsIndex = TileEngine.Instance.LevelWidth - 1;

                for (int column = leftPlayerBoundsIndex; column <= rightPlayerBoundsIndex; column++)
                {
                    if (tileY < playerY) // if tile is above the player
                    {
                        for (int row = playerY; row > tileY; row--)
                        {
                            if (TileEngine.Instance.JumpingGrid[playerX, row].IsWall)
                                return true;
                        }
                    }
                    else if (tileY > playerY) // if tile is below the player
                    {
                        for (int row = playerY; row < tileY; row++)
                        {
                            if (TileEngine.Instance.JumpingGrid[playerX, row].IsWall)
                                return true;
                        }
                    }
                }
                return false;
            }


            /// <summary>
            /// Makes the player/AI walk left.
            /// </summary>
            private void MoveLeft()
            {
                //the player should move left)
                Player.Instance.RightKeyPressed = false;
                Player.Instance.LeftKeyPressed = true;
            }
            /// <summary>
            /// Makes the player/AI walk right.
            /// </summary>
            private void MoveRight()
            {
                //the player should move right
                Player.Instance.LeftKeyPressed = false;
                Player.Instance.RightKeyPressed = true;
            }

            /// <summary>
            /// Get the difference between two integers.
            /// </summary>
            /// <param name="a">Integer a.</param>
            /// <param name="b">Integer a.</param>
            /// <returns>Returns difference between integer a and integer b.</returns>
            private int GetDifference(int a, int b)
            {
                if (a < b)
                    return b - a;
                else return a - b;
            }

            

            /// <summary>
            /// Initializes the A* algorithm, making it ready for TBA* use.
            /// <param name="start">The starting position.</param>
            /// <param name="goalLocation">The goal.</param>
            /// </summary>
            public void InitializeNewPath(Point startLocation, Point goalLocation)
            {
                // Reset all values so this method can be recalled for every new start and goal
                solutionFound = false;
                open.Clear();
                closed.Clear();
                path.Clear();
                if (this.start != null)
                    this.start.IsStart = false;
                if (this.goal != null)
                    this.goal.IsGoal = false;

                jumpTarget = null;
                fallTarget = null;
                mNextTile = null;

                PathfindingTile[,] grid = TileEngine.Instance.JumpingGrid;
                this.start = grid[startLocation.X, startLocation.Y];
                this.goal = grid[goalLocation.X, goalLocation.Y]; // out of range
                this.currentLocation = start;
                this.lastLocation = start;

                // Initialize the start and goal tile
                this.start.IsStart = true; // Start tile = Top left
                this.goal.IsGoal = true;

                open.Add(start);

                // Adjacencies have been calculated during initialization. The adjacencies 
                // are all available tiles, NOT the walls.
                HashSet<PathfindingTile> adjacencies = start.Adjacencies;
                foreach (PathfindingTile adjacency in adjacencies) // Calculate adjacencies
                {
                    adjacency.Parent = start;
                    if (adjacency.IsStart == false)
                    {
                        open.Add(adjacency);
                    }
                }
            }

            /// <summary>
            /// Find the best path using the A*-algorithm.
            /// </summary>
            /// <param name="expansionLimit">The expansion limit of the pathfinding process (maximum number of iterations).</param>
            /// <returns>Whether a solution has been found (meaning the goal has been reached) or not.</returns>
            public bool ContinueAstar(int expansionLimit)
            {
                // Clear the last path, making room for a new one.
                this.path.Clear();

                // Calculate best path to either the goal or the heuristic best 
                // tile if the expansion limit is reached
                int iterations = 0;
                while (open.Count > 0 && iterations < expansionLimit)
                {
                    iterations++;

                    PathfindingTile lowestCost = FindLowestCost();
                    if (lowestCost == goal)
                    {
                        ReconstructPath(goal);
                        return true;
                    }
                    else // Goal hasn't been found yet
                    {
                        open.Remove(lowestCost);
                        closed.Add(lowestCost);

                        foreach (PathfindingTile neighbor in lowestCost.Adjacencies)
                        {
                            if (closed.Contains(neighbor))
                                continue;

                            // Get the cost of the path until the best tile, and the cost of the estimated distance to the 
                            // neighbor tile added together
                            int tentativeParentCost = lowestCost.GetParentCost() + lowestCost.GetHeuristicCost(neighbor);
                            bool tentativeIsBetter = false;

                            if (!open.Contains(neighbor))
                            {
                                open.Add(neighbor);
                                neighbor.HeuristicCost = neighbor.GetHeuristicCost(goal);
                                tentativeIsBetter = true;
                            }
                            // Predicted tentative parent cost is smaller than the actual parent cost of the neighbor
                            else if (tentativeParentCost < neighbor.GetParentCost())
                                tentativeIsBetter = true;
                            else
                                tentativeIsBetter = false;

                            if (tentativeIsBetter)
                            {
                                neighbor.Parent = lowestCost;
                                neighbor.ParentCost = tentativeParentCost;
                                neighbor.EstimatedTotalCost = neighbor.ParentCost + neighbor.HeuristicCost;
                                // Note: Heuristic cost of neighbor has always been set at this stage
                            }
                        }
                    }
                }

                // The last item in closed list is always the best path, and the longest (most progress)
                ReconstructPath(closed.Last());
                return false; // Goal not found
            }

            /// <summary>
            /// Find the tile via which the path (from start to goal) is the shortest.
            /// </summary>
            /// <returns></returns>
            private PathfindingTile FindLowestCost()
            {
                PathfindingTile best = null;
                foreach (PathfindingTile tile in open)
                {
                    if (best == null || tile.GetTotalCost(goal) < best.GetTotalCost(goal))
                    {
                        best = tile;
                    }
                }

                return best;
            }

            /// <summary>
            /// Reconstructs the path, filling the path from the tile after the start tile to the goal tile.
            /// Backtraces from the goal tile to the start tile or current location.
            /// </summary>
            /// <param name="tile">The tile to recursively reconstruct from (the goal).</param>
            private void ReconstructPath(PathfindingTile tile)
            {
                path.Add(tile);

                if (tile.Parent != null && currentLocation != null)
                {
                    // Backtrace using recursion until the start tile or the current location is reached.
                    if (tile.Parent.IsStart == false && tile.Parent != currentLocation)
                    {
                        ReconstructPath(tile.Parent);
                    }
                }
                else return;
            }

            /// <summary>
            /// Executes the Time-Bounded A* algorithm.
            /// </summary>
            public void TbaStar()
            {
                Point tileIndex = Player.Instance.Character.TileIndex;

                if (!solutionFound)
                    solutionFound = ContinueAstar(TbaIterations);

                pathToFollow = this.path;

                // Check if the last tile in the path equals the goal tile
                if (pathToFollow.Count > 0)
                {
                    if (pathToFollow[0] == goal)
                        solutionFound = true;
                }

                if (pathToFollow.Contains(currentLocation) && pathToFollow.Count > 1)
                {
                    // Move one step forward along the path.
                    // Pop the last element in the path, which is the front of the path.
                    RemoveSkippedPath();
                    if (pathToFollow.Count > 0) // Path may be empty after removal
                        currentLocation = pathToFollow[pathToFollow.Count - 1];
                }
                else // Path doesn't contain the current location
                {
                    // Take one step back by backtracking
                    if (currentLocation != start)
                        currentLocation = currentLocation.Parent; // step back
                    else
                        currentLocation = lastLocation;

                    // Set current location of not out of bounds
                    if (tileIndex.X > 0 && tileIndex.X < TileEngine.Instance.JumpingGrid.GetLength(0) && tileIndex.Y > 0 && tileIndex.Y < TileEngine.Instance.JumpingGrid.GetLength(1))
                        currentLocation = TileEngine.Instance.JumpingGrid[tileIndex.X, tileIndex.Y]; // OUT OF BOUNDS
                }
                lastLocation = currentLocation;
                // Set current location of not out of bounds
                if (tileIndex.X > 0 && tileIndex.X < TileEngine.Instance.JumpingGrid.GetLength(0) && tileIndex.Y > 0 && tileIndex.Y < TileEngine.Instance.JumpingGrid.GetLength(1))
                    currentLocation = TileEngine.Instance.JumpingGrid[tileIndex.X, tileIndex.Y];

                // Check if current location is the goal, or check last in path in case no path to goal was found
                if (currentLocation == goal || (pathToFollow.Count > 0 && currentLocation == pathToFollow.First())) 
                {
                    Console.WriteLine("GOAL REACHED! CREATING NEW PATH!");
                    // Execute pathfinding for a new path when done
                    InitializeRandomPath();
                }
            }

            /// <summary>
            /// Removes the part of the path that has been skipped. This situation occurs when the AI 
            /// jumps and lands somewhere in the path, rendering the skipped part of the path useless.
            /// </summary>
            private void RemoveSkippedPath()
            {
                int pathLength = pathToFollow.Count;
                for (int i = pathLength - 1; i >= 0; i--)
                {
                    PathfindingTile skippedTile = pathToFollow[i];
                    pathToFollow.RemoveAt(i);
                    if (skippedTile.Equals(currentLocation))
                        return;
                }
            }

            /// <summary>
            /// Will attempt to shoot at the the closest target if within sight. Prioritizes special shot, and normal shot after that.
            /// </summary>
            private void Shoot()
            {
                List<Opponent> opponents = GameManager.Instance.Opponents;
                foreach (Opponent opponent in opponents)
                {
                    //if opponent is alive and within sight
                    if (!opponent.Character.IsDead 
                        && Math.Abs(Player.Instance.Character.CameraX - opponent.Character.CameraX) < GameManager.Instance.HalfScreenWidth
                        && Math.Abs(Player.instance.Character.CameraY - opponent.Character.CameraY) < GameManager.Instance.HalfScreenHeight)
                    {
                        //place crosshair on player
                        Player.Instance.Crosshair.Position = new Vector2(opponent.Character.CameraX - Camera.X + GameManager.Instance.HalfScreenWidth, opponent.Character.CameraY - Camera.Y + GameManager.Instance.HalfScreenHeight);
                        Player.Instance.Crosshair.Direction = Player.Instance.Crosshair.Position - Player.Instance.AbsoluteProjectileOrigin;
                        Player.Instance.Crosshair.Direction.Normalize();
                        //Shoot in crosshair direction (Player functions will handle cooldown)
                        //Try special first, and normal otherwise.
                        Player.instance.ShootSpecial();
                        Player.Instance.Shoot(); 
                    }
                }
            }

            /// <summary>
            /// Draws the AI agent.
            /// </summary>
            public void Draw(SpriteBatch spriteBatch)
            {
                if (Enabled)
                {
                    if (start != null)
                    {
                        Rectangle drawRect = new Rectangle((start.X * Tile.Width) - Camera.X, (start.Y * Tile.Height) - Camera.Y, Tile.Width, Tile.Height);
                        spriteBatch.Draw(mDummyTexture, drawRect, Color.Black);
                    }
                    if (goal != null)
                    {
                        Rectangle drawRect = new Rectangle((goal.X * Tile.Width) - Camera.X, (goal.Y * Tile.Height) - Camera.Y, Tile.Width, Tile.Height);
                        spriteBatch.Draw(mDummyTexture, drawRect, Color.Red);
                    }
                    else Console.WriteLine("GOAL IS NULL!!!");

                    foreach (PathfindingTile tile in this.pathToFollow)
                    {
                        Rectangle drawRect = new Rectangle((tile.X * Tile.Width) - Camera.X, (tile.Y * Tile.Height) - Camera.Y, Tile.Width, Tile.Height);
                        spriteBatch.Draw(mDummyTexture, drawRect, Color.DarkGreen * 0.5f);
                    }

                    if (mNextTile != null)
                    {
                        Rectangle drawRect = new Rectangle((mNextTile.X * Tile.Width) - Camera.X, (mNextTile.Y * Tile.Height) - Camera.Y, Tile.Width, Tile.Height);
                        spriteBatch.Draw(mDummyTexture, drawRect, Color.White * 0.5f);
                    }

                    if (fallTarget != null)
                    {
                        Rectangle drawRect = new Rectangle((fallTarget.X * Tile.Width) - Camera.X, (fallTarget.Y * Tile.Height) - Camera.Y, Tile.Width, Tile.Height);
                        spriteBatch.Draw(mDummyTexture, drawRect, Color.Yellow * 0.5f);
                    }
                }
            }
        }

        #endregion
    }
}
